import { commonUtil } from '../common-util';

import { TreeMap } from './models/common-model';
import { ModuleTemplateType } from './models/constants';
import {
  defaultProjectIndex,
  ModuleTemplateOption,
  ProjectTemplateIndex,
  ProjectTemplateOption
} from './models/model-options';
import { Module } from './models/module-models';
import { Project, ProjectConfigInitFun } from './models/project-models';
import { DirTemplateCreator } from './models/template-models';
import { DefaultTemplate } from './template/template-init';
import { moduleBuilder } from './module-builder';

/**
 * 创建一个工程对象的方法
 */
export type ProjectCreator = (projectName: string, option: ProjectTemplateOption) => Project

/**
 * 工程子模块结构对象
 * 可以是 自定义模块名和子模块属性/关键函数 的对象
 * 或 子模块类型数组
 */
export type ModulesOption = {
    [moduleName: string]: ModuleTemplateOption | Module;
  }
  | ModuleTemplateType[]
  | Partial<ModuleTemplateTypeCount>;

/**
 * 用来记录模块类型格式的类型
 */
type ModuleTemplateTypeCount = {
  [key in ModuleTemplateType]: number;
}

/**
 * 存储或查询 创建常见project对象的函数 的构建类
 */
class ProjectBuilder {
  private readonly projectCreatorMap: TreeMap<ProjectCreator, ProjectTemplateIndex> =
    new TreeMap<ProjectCreator, ProjectTemplateIndex>(defaultProjectIndex);

  /**
   * 查找工程生成方法
   *
   * @param {ProjectTemplateIndex} index
   * @param modulesOption 模块选项
   * @return {ProjectCreator | undefined}
   */
  public findProjectCreator(index: ProjectTemplateIndex, modulesOption?: ModulesOption): ProjectCreator | undefined {
    const projectCreator = this.projectCreatorMap.find(index);
    if (projectCreator === undefined) {
      return undefined;
    }
    return (projectName, option) => {
      const project = projectCreator(projectName, option);
      this.initModules(project, modulesOption);
      return project;
    };
  }

  /**
   * 添加工程创建方法
   *
   * @param {DirTemplateCreator} template
   * @param {ModuleTemplateOption} index
   * @param {ModuleConfigInitFun} initFun
   */
  public add(index: ProjectTemplateIndex, template: DirTemplateCreator, initFun?: ProjectConfigInitFun) {
    this.projectCreatorMap.insert({ ...index }, (projectName, option) => {
      return new Project(template(projectName), option, initFun);
    });
  }

  /**
   * 通过传入的模板类型给工程增加对应子模块
   *
   * @param {Project} project
   * @param {ModulesOption} modulesOption
   * @private
   */
  private initModules(project: Project, modulesOption?: ModulesOption) {
    if (modulesOption === undefined) {
      return;
    }
    if (commonUtil.toType(modulesOption) === 'Array') {
      const typeCount: Partial<ModuleTemplateTypeCount> = {};
      for (const moduleType of modulesOption as Array<ModuleTemplateType>) {
        if (typeof typeCount[moduleType] === 'number') {
          typeCount[moduleType]!++;
        } else {
          typeCount[moduleType] = 1;
        }
      }
      this.initModulesByTypeCount(project, typeCount);
    } else if (typeof Object.values(modulesOption)[0] === 'number') {
      this.initModulesByTypeCount(project, modulesOption as ModuleTemplateTypeCount);
    } else {
      // 传入的是对象，直接遍历键值对生成module对象填入project
      for (const [moduleName, moduleOption] of Object.entries(modulesOption)) {
        if (moduleOption instanceof Module) {
          project.addModule(moduleName, moduleOption);
        } else {
          project.addModule(moduleName, DefaultTemplate.module(moduleOption)(moduleName, moduleOption));
        }
      }
    }
  }

  /**
   * 根据不同类型模块的个数给project组装子模块
   *
   * @param {Project} project
   * @param {ModuleTemplateTypeCount} typeCount
   * @private
   */
  private initModulesByTypeCount(project: Project, typeCount: Partial<ModuleTemplateTypeCount>) {
    const commonOption = {
      runtimeOS: project.option.runtimeOS,
      apiVersion: project.option.apiVersion,
      apiType: project.option.apiType,
      language: project.option.language,
      abilityType: project.option.abilityType
    };
    for (const [moduleType, count] of Object.entries(typeCount)) {
      if (count === 0) {
        continue;
      }
      const moduleOption: ModuleTemplateOption = {
        ...commonOption,
        moduleTemplateType: moduleType as ModuleTemplateType
      };
      const moduleCreator = moduleBuilder.findModuleCreator(moduleOption);
      if (moduleCreator === undefined) {
        throw new Error(`No matching template type found: ${JSON.stringify(moduleOption)}`);
      }
      for (let i = 0; i < count; i++) {
        const moduleName = i === 0 ? moduleType : `${moduleType}${i}`;
        project.addModule(moduleName, moduleCreator(moduleName, moduleOption));
      }
    }
  }
}

export const projectBuilder = new ProjectBuilder();
